using System.Collections.Concurrent;
using System.Diagnostics;
using OpenCvSharp;
using VideoRecorder.Util;
using Size = OpenCvSharp.Size;

namespace VideoRecorder.Media;

public class CvVideoWriter {
    private const string TAG = $"[{nameof(CvVideoWriter)}]";
    private readonly ConcurrentQueue<Mat> _framesQueue = new();
    private VideoWriter _writer;
    private readonly object _writeLocker = new();
    private readonly TimedIntervalTask _writerTask;
    private int _totalFrames;

    public int TotalFrames => _totalFrames;

    public CvVideoWriter() {
        _writerTask = new TimedIntervalTask(WriteVideoTaskRunner, 1000);
    }

    public void AddFrame(Mat frame) {
        _framesQueue.Enqueue(frame);
        Interlocked.Increment(ref _totalFrames);
    }

    public void Open(string outputFile, int width, int height, int fps) {
        if (_writer != null) return;
        _framesQueue.Clear();
        _totalFrames = 0;

        lock (_writeLocker) {
            _writer = new VideoWriter(outputFile, FourCC.H264, fps, new Size(width, height));
            _writer.Set(VideoWriterProperties.Quality, 75);
        }

        _writerTask.Startup();
        Debug.WriteLine($"{TAG} 开始录制: {outputFile} {width}x{height}@{fps}fps");
    }

    public void Close() {
        if (_writer == null) return;
        Task.Run(async () => {
            Debug.WriteLine($"{TAG} 停止录制: {_totalFrames} frames, {_writer?.FileName}");
            if (_writer != null) {
                while (true) {
                    await Task.Delay(250);
                    if (_framesQueue.IsEmpty) {
                        _writerTask.Stop();
                        await Task.Delay(250);
                        break;
                    }
                }
            }
        }).ContinueWith(_ => {
            Debug.WriteLine($"{TAG} 完成录制: {_totalFrames} frames, {_writer?.FileName}");
            lock (_writeLocker) {
                _writer?.Release();
                _writer?.Dispose();
                _writer = null;
            }
        });
    }

    private void WriteVideoTaskRunner() {
        var count = _framesQueue.Count;
        if (count > 0) {
            Mat mat = null;
            for (var i = 0; i < count; i++) {
                try {
                    if (_framesQueue.TryDequeue(out mat)) {
                        using var input = InputArray.Create(mat);
                        lock (_writeLocker) _writer?.Write(input);
                    }
                } catch (Exception ex) {
                    Debug.WriteLine(ex);
                } finally {
                    mat?.Dispose();
                }
            }
        }
    }
}
